package main

import (
	"bufio"
	"encoding/gob"
	"encoding/json"
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"io"
	"log"
	"net"
	"net/http"
	"sync"
)

var (
	DB     *gorm.DB
)


type Article struct {
	gorm.Model
	Title    string    	`json:"title" gorm:"comment:标题"`
	UserId	  int			`gorm:"not null"`
	UserRefer 	 SysUser		`json:"user" gorm:"column:user_id;comment:关联的s标记;foreignKey:UserId"`
}


type SysUser struct {
	gorm.Model
	Username    string       `json:"username" gorm:"comment:用户登录名"`
	Password      string     `json:"password" gorm:"comment:密码"`
	Articles    []Article	 `json:"articles" gorm:"foreignKey:UserRefer;AssociationForeignKey:UserRefer" `
	Corses      []Corse		 `json:"corses" gorm:"many2many:user_corse;"`

}


type Corse struct {
	gorm.Model
	Name 	 string       `json:"name" gorm:"comment:课程名"`
	Users    []SysUser	  `json:"users" gorm:"many2many:user_corse;"`
}

func main(){

    dsn := "root:123qwe@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
	mysqlConfig := mysql.Config{
		DSN:                       dsn,   // DSN data source name
		DefaultStringSize:         191,   // string 类型字段的默认长度
		DisableDatetimePrecision:  true,  // 禁用 datetime 精度，MySQL 5.6 之前的数据库不支持
		DontSupportRenameIndex:    true,  // 重命名索引时采用删除并新建的方式，MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
		DontSupportRenameColumn:   true,  // 用 `change` 重命名列，MySQL 8 之前的数据库和 MariaDB 不支持重命名列
		SkipInitializeWithVersion: false, // 根据版本自动配置
	}
	gormConfig := &gorm.Config{
		Logger:                                   logger.Default.LogMode(logger.Info),
		DisableForeignKeyConstraintWhenMigrating: true,
	}
	var  err error
	if DB, err = gorm.Open(mysql.New(mysqlConfig), gormConfig); err != nil {

		fmt.Println(11111, err)

	}else{

		err := DB.AutoMigrate(
			SysUser{},
			Article{},
			Corse{},
			)

		if err != nil{
		    fmt.Println(22222)
			return
		}
		fmt.Println("register table success")
	}

	//多对多、一对多 查询
	var user  SysUser
	err  = DB.Where("username=?", "zhanglin").Preload("Articles").Preload("Corses").First(&user).Error	//查询
	if err != nil{
		fmt.Println(err)
	}else{
		fmt.Println(user.Articles)
		fmt.Println(user.Corses)
	}


	//一对多反向查询
	var article Article
	err = DB.Where("id=?", 1).Preload("UserRefer").// Copyright 2009 The Go Authors. All rights reserved.
	// Use of this source code is governed by a BSD-style
	// license that can be found in the LICENSE file.

	package rpc

	import (
		"bufio"
	"encoding/gob"
	"errors"
	"io"
	"log"
	"net"
	"net/http"
	"sync"
	)

	// ServerError represents an error that has been returned from
	// the remote side of the RPC connection.
	type ServerError string

	func (e ServerError) Error() string {
		return string(e)
	}

	var ErrShutdown = errors.New("connection is shut down")

	// Call represents an active RPC.
	type Call struct {
		ServiceMethod string      // The name of the service and method to call.
		Args          interface{} // The argument to the function (*struct).
		Reply         interface{} // The reply from the function (*struct).
		Error         error       // After completion, the error status.
		Done          chan *Call  // Strobes when call is complete.
	}

	// Client represents an RPC Client.
	// There may be multiple outstanding Calls associated
	// with a single Client, and a Client may be used by
	// multiple goroutines simultaneously.
	type Client struct {
		codec ClientCodec

		reqMutex sync.Mutex // protects following
		request  Request

		mutex    sync.Mutex // protects following
		seq      uint64
		pending  map[uint64]*Call
		closing  bool // user has called Close
		shutdown bool // server has told us to stop
	}

	// A ClientCodec implements writing of RPC requests and
	// reading of RPC responses for the client side of an RPC session.
	// The client calls WriteRequest to write a request to the connection
	// and calls ReadResponseHeader and ReadResponseBody in pairs
	// to read responses. The client calls Close when finished with the
	// connection. ReadResponseBody may be called with a nil
	// argument to force the body of the response to be read and then
	// discarded.
	// See NewClient's comment for information about concurrent access.
	type ClientCodec interface {
		WriteRequest(*Request, interface{}) error
		ReadResponseHeader(*Response) error
		ReadResponseBody(interface{}) error

		Close() error
	}

	func (client *Client) send(call *Call) {
		client.reqMutex.Lock()
		defer client.reqMutex.Unlock()

		// Register this call.
		client.mutex.Lock()
		if client.shutdown || client.closing {
			client.mutex.Unlock()
			call.Error = ErrShutdown
			call.done()
			return
		}
		seq := client.seq
		client.seq++
		client.pending[seq] = call  //把call实例保存到全局请求列表里
		client.mutex.Unlock()

		// Encode and send the request.
		client.request.Seq = seq
		client.request.ServiceMethod = call.ServiceMethod
		err := client.codec.WriteRequest(&client.request, call.Args)
		if err != nil {
			client.mutex.Lock()
			call = client.pending[seq]
			delete(client.pending, seq)
			client.mutex.Unlock()
			if call != nil {
				call.Error = err
				call.done()
			}
		}
	}

	func (client *Client) input() {
		var err error
		var response Response
		for err == nil {
			response = Response{}
			err = client.codec.ReadResponseHeader(&response)
			if err != nil {
				break
			}
			seq := response.Seq
			client.mutex.Lock()
			call := client.pending[seq]
			delete(client.pending, seq)
			client.mutex.Unlock()

			switch {
			case call == nil:
				// We've got no pending call. That usually means that
				// WriteRequest partially failed, and call was already
				// removed; response is a server telling us about an
				// error reading request body. We should still attempt
				// to read error body, but there's no one to give it to.
				err = client.codec.ReadResponseBody(nil)
				if err != nil {
					err = errors.New("reading error body: " + err.Error())
				}
			case response.Error != "":
				// We've got an error response. Give this to the request;
				// any subsequent requests will get the ReadResponseBody
				// error if there is one.
				call.Error = ServerError(response.Error)
				err = client.codec.ReadResponseBody(nil)
				if err != nil {
					err = errors.New("reading error body: " + err.Error())
				}
				call.done()
			default:
				err = client.codec.ReadResponseBody(call.Reply)  //解析返回的数据生成call.Reply结构体
				if err != nil {
					call.Error = errors.New("reading body " + err.Error())
				}
				call.done()
			}
		}
		// Terminate pending calls.
		client.reqMutex.Lock()
		client.mutex.Lock()
		client.shutdown = true
		closing := client.closing
		if err == io.EOF {
			if closing {
				err = ErrShutdown
			} else {
				err = io.ErrUnexpectedEOF
			}
		}
		for _, call := range client.pending {
			call.Error = err
			call.done()
		}
		client.mutex.Unlock()
		client.reqMutex.Unlock()
		if debugLog && err != io.EOF && !closing {
			log.Println("rpc: client protocol error:", err)
		}
	}

	func (call *Call) done() {
		select {
		case call.Done <- call:
			// ok
		default:
			// We don't want to block here. It is the caller's responsibility to make
			// sure the channel has enough buffer space. See comment in Go().
			if debugLog {
				log.Println("rpc: discarding Call reply due to insufficient Done chan capacity")
			}
		}
	}

	// NewClient returns a new Client to handle requests to the
	// set of services at the other end of the connection.
	// It adds a buffer to the write side of the connection so
	// the header and payload are sent as a unit.
	//
	// The read and write halves of the connection are serialized independently,
	// so no interlocking is required. However each half may be accessed
	// concurrently so the implementation of conn should protect against
	// concurrent reads or concurrent writes.
	func NewClient(conn io.ReadWriteCloser) *Client {
		encBuf := bufio.NewWriter(conn)
		//gobClientCodec结构体实现了ClientCodec接口
		client := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf}
		return NewClientWithCodec(client)
	}

	// NewClientWithCodec is like NewClient but uses the specified
	// codec to encode requests and decode responses.
	//这里的ClientCodec为接口
	func NewClientWithCodec(codec ClientCodec) *Client {
		client := &Client{
		codec:   codec,
		pending: make(map[uint64]*Call),
	}
		go client.input()  //开启一个协程获取连接返回的数据
		return client
	}

	type gobClientCodec struct {
		rwc    io.ReadWriteCloser
		dec    *gob.Decoder
		enc    *gob.Encoder
		encBuf *bufio.Writer
	}

	//实现的WriteRequest
	func (c *gobClientCodec) WriteRequest(r *Request, body interface{}) (err error) {
	if err = c.enc.Encode(r); err != nil {
	return
	}
	if err = c.enc.Encode(body); err != nil {
	return
	}
	return c.encBuf.Flush()  //最终发送数据
	}

	func (c *gobClientCodec) ReadResponseHeader(r *Response) error {
	return c.dec.Decode(r)
	}

	func (c *gobClientCodec) ReadResponseBody(body interface{}) error {
	return c.dec.Decode(body)
	}

	func (c *gobClientCodec) Close() error {
	return c.rwc.Close()
	}

	// DialHTTP connects to an HTTP RPC server at the specified network address
	// listening on the default HTTP RPC path.
	func DialHTTP(network, address string) (*Client, error) {
	return DialHTTPPath(network, address, DefaultRPCPath)
	}

	// DialHTTPPath connects to an HTTP RPC server
	// at the specified network address and path.
	func DialHTTPPath(network, address, path string) (*Client, error) {
	var err error
	conn, err := net.Dial(network, address)
	if err != nil {
	return nil, err
	}
	io.WriteString(conn, "CONNECT "+path+" HTTP/1.0\n\n")   //向目标服务器建立HTTP连接，请求路径为/_goRPC_，请求方法为CONNECT,CONNECT方法是需要使用tcp去直接连接的，所以不适合在网页开发中使用
	// Require successful HTTP response
	// before switching to RPC protocol.
	resp, err := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: "CONNECT"})
	if err == nil && resp.Status == connected {
	return NewClient(conn), nil
	}
	if err == nil {
	err = errors.New("unexpected HTTP response: " + resp.Status)
	}
	conn.Close()
	return nil, &net.OpError{
	Op:   "dial-http",
	Net:  network + " " + address,
	Addr: nil,
	Err:  err,
	}
	}

	// Dial connects to an RPC server at the specified network address.
	func Dial(network, address string) (*Client, error) {
	conn, err := net.Dial(network, address)
	if err != nil {
	return nil, err
	}
	return NewClient(conn), nil
	}

	// Close calls the underlying codec's Close method. If the connection is already
	// shutting down, ErrShutdown is returned.
	func (client *Client) Close() error {
	client.mutex.Lock()
	if client.closing {
	client.mutex.Unlock()
	return ErrShutdown
	}
	client.closing = true
	client.mutex.Unlock()
	return client.codec.Close()
	}

	// Go invokes the function asynchronously. It returns the Call structure representing
	// the invocation. The done channel will signal when the call is complete by returning
	// the same Call object. If done is nil, Go will allocate a new channel.
	// If non-nil, done must be buffered or Go will deliberately crash.
	func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call {
	call := new(Call)   //构造一个Call实例，用于返回给客户端处理
	call.ServiceMethod = serviceMethod
	call.Args = args
	call.Reply = reply    //这里很关键，因为是引用传递，所以call.Reply有变化时会同步到reply
	if done == nil {
	done = make(chan *Call, 10) // buffered.
	} else {
	// If caller passes done != nil, it must arrange that
	// done has enough buffer for the number of simultaneous
	// RPCs that will be using that channel. If the channel
	// is totally unbuffered, it's best not to run at all.
	if cap(done) == 0 {
	log.Panic("rpc: done channel is unbuffered")
	}
	}
	call.Done = done
	client.send(call)
	return call   //
	}

	// Call invokes the named function, waits for it to complete, and returns its error status.
	func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error {
	call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Done
	return call.Error
	}

	(&article).Error
	if err != nil{
		fmt.Println(err)
	}else{
		fmt.Println("444444444", article)
	}


	result, _ := json.Marshal(article)
	fmt.Println(string(result))

	//err = DB.Where("username=?", "zhanglin").First(&user).Updates(map[string]interface{}{"password":"123456"}).Error
	err = DB.Model(&SysUser{}).Where("username=?", "zhanglin").Updates(map[string]interface{}{"password":"123456"}).Error
	if err != nil{
		fmt.Println(err)
	}else{
		fmt.Println(user.Username)
	}


	var corse  Corse
	//err = DB.Where("id = ?", 3).Delete(&corse).Error
	err = DB.Where(Corse{Name: "wuli"}).Delete(&corse).Error
	if err !=nil{
		fmt.Println(err)
	}else{
		fmt.Println("删除成功")
	}


	//新增
	u := SysUser{
		Username: "wangwu",
		Password: "123qwe",
	}
	if err = DB.Create(&u).Error; err != nil {
		fmt.Println("插入失败", err)

	}else{
		fmt.Println("插入成功")
	}


	//多对多插入
	var c Corse
	err = DB.Where("id=?", 3).First(&c).Error
	if err != nil{
		fmt.Println(err)
	}else{
		fmt.Println(c.Name)
	}
	DB.Model(&user).Association("Corses").Append(&c)

	//一对多 插入


}